package com.newx.auth.license.tool;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.ag.license.domain.License;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;

/**
 * 许可证工具类
 * @Created by yxl
 * @Date 2025/2/15 15:09
 */
public class LicenseTool {

    private static final Logger log = LoggerFactory.getLogger(LicenseTool.class);

    private static final String LICENSE_FILENAME = "license.lic";


    private static final ObjectMapper objectMapper = new ObjectMapper()
            .registerModule(new JavaTimeModule())// // 注册 JavaTimeModule 以支持 Java 8 日期时间类型（包括 Timestamp）
            //.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// // 禁用未知属性导致的反序列化失败

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 获取硬件指纹
     *
     * @return 硬件指纹
     * @throws Exception 如果发生错误
     */
    public static String getHardwareFingerprint() throws Exception {
        InetAddress inetAddress = InetAddress.getLocalHost();
        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
        byte[] hardwareAddress = networkInterface.getHardwareAddress();

        if (hardwareAddress == null) {
            throw new IllegalStateException("无法获取硬件地址");
        }

        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(hardwareAddress);
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    /**
     * 生成许可证文件
     *
     * @param privateKeyPath  私钥路径
     * @param licenseFilePath 许可证文件路径,注意路径后缀区分linux和windows有没有"\\" 或者 "/"
     *                        默认为"license.lic"
     * @param validFrom       有效期起始时间
     * @param validEnd        有效期结束时间
     * @throws Exception 如果发生错误
     */
    public static String generateLicense(String privateKeyPath, String licenseFilePath, Date validFrom, Date validEnd) throws Exception {
        if(StrUtil.hasBlank(privateKeyPath)){
            throw new IllegalArgumentException("Private key path cannot be blank.");
        }
        if(StrUtil.hasBlank(licenseFilePath)){
            throw new IllegalArgumentException("License file path cannot be blank.");
        }
        if(validFrom == null){
            throw new IllegalArgumentException("Valid from date cannot be null.");
        }
        if(validEnd == null){
            throw new IllegalArgumentException("validEnd cannot be null.");
        }
        // 判断是否有文件
        if (!Files.exists(Paths.get(privateKeyPath))) {
            // 抛出文件找不到异常
            throw new FileNotFoundException("私钥文件不存在。");
        }
        // 判断licenseFilePath存不存在，如果不存在就创建一个空文件夹
        File newFile = new File(licenseFilePath);
        if (!newFile.exists()) {
            // 创建完整的目录
            FileUtil.mkdir(licenseFilePath);
        }
        licenseFilePath = formatFilePath(licenseFilePath);
        // 读取私钥
        PrivateKey privateKey = readPrivateKey(privateKeyPath);

        // 获取硬件指纹（示例）
        String fingerprint = getHardwareFingerprint();
        // 将Date转换为Timestamp
        Timestamp startTime = new Timestamp(validFrom.getTime());// 固定时间戳（例如 2023-01-01 00:00:00 UTC）
        Timestamp endTime = new Timestamp(validEnd.getTime());
        log.info("有效期开始时间：{}", DateUtil.format(validFrom, DatePattern.PURE_DATETIME_PATTERN));
        log.info("有效期结束时间：{}", DateUtil.format(validEnd, DatePattern.PURE_DATETIME_PATTERN));
        String format = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
        // 创建许可证对象
        License license = new License(
                "LIC" + format,
                fingerprint,
                startTime,
                endTime
        );

        // 序列化为JSON
        String jsonString = objectMapper.writeValueAsString(license);
        // 签名
        byte[] signature = signData(jsonString.getBytes(StandardCharsets.UTF_8), privateKey);
        license.setSignature(Base64.getEncoder().encodeToString(signature));

        // 再次序列化为JSON
        String signedJsonString = objectMapper.writeValueAsString(license);
        // 加密
        SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode("TD9RbFQZcDl+TnHoYPL2ArRVZBjpP+oHGKVU5kf9iXU="), LicenseEncryptorTool.ALGORITHM);
        String encrypt = LicenseEncryptorTool.encrypt(signedJsonString, secretKey);

        // 写入许可证文件
        // FileWriter writer = new FileWriter(licenseFilePath + LICENSE_FILENAME);
        FileOutputStream fos = new FileOutputStream(licenseFilePath + LICENSE_FILENAME);
        fos.write(encrypt.getBytes());
        return "许可证文件已生成并保存到 " + licenseFilePath + LICENSE_FILENAME;
    }

    /**
     * 生成许可证内容（不保存到本地）
     *
     * @param privateKey  私钥对象
     * @param validFrom   有效期起始时间
     * @param validEnd    有效期结束时间
     * @return 许可证内容字符串
     * @throws Exception 如果发生错误
     */
    public static String generateLicense(PrivateKey privateKey, Date validFrom, Date validEnd) throws Exception {
        if (validFrom == null) {
            throw new IllegalArgumentException("Valid from date cannot be null.");
        }
        if (validEnd == null) {
            throw new IllegalArgumentException("validEnd cannot be null.");
        }

        // 获取硬件指纹
        String fingerprint = getHardwareFingerprint();

        // 创建许可证对象
        Timestamp startTime = new Timestamp(validFrom.getTime());
        Timestamp endTime = new Timestamp(validEnd.getTime());
        log.info("有效期开始时间：{}", DateUtil.format(validFrom, DatePattern.PURE_DATETIME_PATTERN));
        log.info("有效期结束时间：{}", DateUtil.format(validEnd, DatePattern.PURE_DATETIME_PATTERN));
        String format = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
        License license = new License(
                "LIC" + format,
                fingerprint,
                startTime,
                endTime
        );

        // 序列化为JSON
        String jsonString = objectMapper.writeValueAsString(license);
        // 签名
        byte[] signature = signData(jsonString.getBytes(StandardCharsets.UTF_8), privateKey);
        license.setSignature(Base64.getEncoder().encodeToString(signature));

        // 再次序列化为JSON
        String signedJsonString = objectMapper.writeValueAsString(license);
        // 加密
        SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode("TD9RbFQZcDl+TnHoYPL2ArRVZBjpP+oHGKVU5kf9iXU="), LicenseEncryptorTool.ALGORITHM);
        String encrypt = LicenseEncryptorTool.encrypt(signedJsonString, secretKey);

        return encrypt;
    }

    /**
     * 读取私钥
     *
     * @param path 私钥路径
     * @return 私钥
     * @throws Exception 如果发生错误
     */
    public static PrivateKey readPrivateKey(String path) throws Exception {
        try (PEMParser pemParser = new PEMParser(new FileReader(path))) {
            Object object = pemParser.readObject();
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
            if (object instanceof PrivateKeyInfo) {
                return converter.getPrivateKey((PrivateKeyInfo) object);
            }
            throw new IllegalArgumentException("Not a valid private key file.");
        } catch (IOException e) {
            throw new IOException("Error reading private key file: " + e.getMessage(), e);
        }
    }

    /**
     * 签名数据
     *
     * @param data       要签名的数据
     * @param privateKey 私钥
     * @return 签名结果
     * @throws Exception 如果发生错误
     */
    public static byte[] signData(byte[] data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA", "BC");
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    /**
     * 验证许可证是否有效
     *
     * @param publicKeyPath  公钥路径
     * @param licenseFilePath 许可证文件路径
     * @return 如果许可证有效，则返回true，否则返回false
     * @throws Exception 如果发生错误
     */
    public static boolean isValidLicenseByFile(String publicKeyPath, String licenseFilePath) throws Exception {
        if(StrUtil.hasBlank(publicKeyPath)){
            throw new IllegalArgumentException("Public key path cannot be blank.");
        }
        if(StrUtil.hasBlank(licenseFilePath)){
            throw new IllegalArgumentException("License file path cannot be blank.");
        }
        // 判断是否有文件
        if (!Files.exists(Paths.get(publicKeyPath))) {
            throw new FileNotFoundException("公钥文件不存在。");
        }
        if (!Files.exists(Paths.get(licenseFilePath))) {
            throw new FileNotFoundException("许可证文件不存在。");
        }
        // 读取公钥
        PublicKey publicKey = readPublicKey(publicKeyPath);

        // 读取许可证文件
        byte[] fileContent = Files.readAllBytes(Paths.get(licenseFilePath));
        // byte[]转字符串
        String signedJsonString = new String(fileContent, StandardCharsets.UTF_8);

        // 加密
        SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode("TD9RbFQZcDl+TnHoYPL2ArRVZBjpP+oHGKVU5kf9iXU="), LicenseEncryptorTool.ALGORITHM);
        String encrypt = LicenseEncryptorTool.decrypt(signedJsonString, secretKey);

        License license = objectMapper.readValue(encrypt.getBytes(), License.class);

        // 验证硬件指纹
        if (!license.getHardwareFingerprint().equals(getHardwareFingerprint())) {
            throw new RuntimeException("硬件指纹不匹配。");
        }

        // 验证有效期
        Instant now = Instant.now();
        if (now.isBefore(license.getValidFrom().toInstant()) || now.isAfter(license.getValidTo().toInstant())) {
            throw new RuntimeException("许可证已过期。");
        }
        // 验证签名
        String base64Signature = license.getSignature();// 得到签名
        license.setSignature(null);
        String jsonString = objectMapper.writeValueAsString(license);

        byte[] dataToVerify = jsonString.getBytes(StandardCharsets.UTF_8);

        byte[] signatureBytes = Base64.getDecoder().decode(base64Signature);

        if (!verifySignature(dataToVerify, signatureBytes, publicKey)) {
            throw new RuntimeException("许可证签名验证失败。");
        }
        log.info("许可证验证成功");
        return true;
    }

    /**
     * 验证许可证是否有效
     *
     * @param publicKeyPath  公钥路径
     * @param licenseContent 许可证内容字符串
     * @return 如果许可证有效，则返回true，否则返回false
     * @throws Exception 如果发生错误
     */
    public static boolean isValidLicenseByContent(String publicKeyPath, String licenseContent) throws Exception {
        if (StrUtil.hasBlank(publicKeyPath)) {
            throw new IllegalArgumentException("Public key path cannot be blank.");
        }
        if (StrUtil.hasBlank(licenseContent)) {
            throw new IllegalArgumentException("License content cannot be blank.");
        }
        // 读取公钥
        PublicKey publicKey = readPublicKey(publicKeyPath);

        // 解密
        SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode("TD9RbFQZcDl+TnHoYPL2ArRVZBjpP+oHGKVU5kf9iXU="), LicenseEncryptorTool.ALGORITHM);
        String decrypt = LicenseEncryptorTool.decrypt(licenseContent, secretKey);

        License license = objectMapper.readValue(decrypt.getBytes(), License.class);

        // 验证硬件指纹
        if (!license.getHardwareFingerprint().equals(getHardwareFingerprint())) {
            throw new RuntimeException("硬件指纹不匹配。");
        }

        // 验证有效期
        Instant now = Instant.now();
        if (now.isBefore(license.getValidFrom().toInstant()) || now.isAfter(license.getValidTo().toInstant())) {
            throw new RuntimeException("许可证已过期。");
        }

        // 验证签名
        String base64Signature = license.getSignature();
        license.setSignature(null);
        String jsonString = objectMapper.writeValueAsString(license);

        byte[] dataToVerify = jsonString.getBytes(StandardCharsets.UTF_8);
        byte[] signatureBytes = Base64.getDecoder().decode(base64Signature);

        if (!verifySignature(dataToVerify, signatureBytes, publicKey)) {
            throw new RuntimeException("许可证签名验证失败。");
        }
        log.info("许可证验证成功");
        return true;
    }

    public static PublicKey readPublicKey(String path) throws Exception {
        try (PEMParser pemParser = new PEMParser(new FileReader(path))) {
            Object object = pemParser.readObject();
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
            if (object instanceof SubjectPublicKeyInfo) {
                return converter.getPublicKey((SubjectPublicKeyInfo) object);
            }
            throw new IllegalArgumentException("Not a valid public key file.");
        } catch (IOException e) {
            throw new IOException("Error reading public key file: " + e.getMessage(), e);
        }
    }

    /**
     * 验签
     * @param data
     * @param signature
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static boolean verifySignature(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
        Signature signatureInstance = Signature.getInstance("SHA256withRSA", "BC");
        signatureInstance.initVerify(publicKey);
        signatureInstance.update(data);
        return signatureInstance.verify(signature);
    }

    /**
     * 格式化文件路径
     * @param filePath
     * @return
     */
    public static String formatFilePath(String filePath){
        if(System.getProperty("os.name").contains("Windows")){
            if (!filePath.endsWith("\\")) {
                filePath = filePath + "\\";
            }
        }
        if(System.getProperty("os.name").contains("Linux")){
            if (!filePath.endsWith("/")) {
                filePath = filePath + "/";
            }
        }
        return filePath;
    }
}
